package net.wyf.codetest.widget;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.support.annotation.ColorInt;
import android.support.annotation.ColorRes;
import android.support.annotation.DrawableRes;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.widget.ImageView;

import net.wyf.codetest.R;

/**
 * 圆形图片
 */

public class CircleImageView extends ImageView {

    private static final ScaleType SCALE_TYPE = ScaleType.CENTER_CROP;

    private static final Bitmap.Config BITMAP_CONFIG = Bitmap.Config.ARGB_8888;
    private static final int COLORDRAWABLE_DIMENSION = 2;

    private static final int DEFAULT_BORDER_WIDTH = 0;
    private static final int DEFAULT_BORDER_COLOR = Color.BLACK;

    private int cBorderColor;
    private int cBorderWidth;

    private final RectF cDrawableRect = new RectF();
    private final RectF cBorderRect = new RectF();

    private final Matrix cShaderMatrix = new Matrix();
    private final Paint cBitmapPaint = new Paint();
    private final Paint cBorderPaint = new Paint();

    private Bitmap cBitmap;
    private BitmapShader cBitmapShader;
    private int cBitmapWidth;
    private int cBitmapHeight;

    private float cDrawableRadius;
    private float cBorderRadius;

    private ColorFilter cColorFilter;

    private boolean cReady;
    private boolean cSetupPending;
    private boolean cDisableCircularTransformation;

    public CircleImageView(Context context) {
        this(context, null);
        init();
    }

    public CircleImageView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CircleImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray attributes = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView, defStyleAttr, 0);
        cBorderColor = attributes.getColor(R.styleable.CircleImageView_circle_border_color, DEFAULT_BORDER_COLOR);
        cBorderWidth = attributes.getDimensionPixelSize(R.styleable.CircleImageView_circle_border_width, DEFAULT_BORDER_WIDTH);

        attributes.recycle();

        init();
    }

    private void init() {
        super.setScaleType(SCALE_TYPE);
        cReady = true;

        if (cSetupPending) {
            setup();
            cSetupPending = false;
        }
    }

    @Override
    public ScaleType getScaleType() {
        return SCALE_TYPE;
    }

    @Override
    public void setScaleType(ScaleType scaleType) {
        if (scaleType != SCALE_TYPE) {
            throw new IllegalArgumentException(String.format("ScaleType %s not supported.", scaleType));
        }
    }

    @Override
    public void setAdjustViewBounds(boolean adjustViewBounds) {
        if (adjustViewBounds) {
            throw new IllegalArgumentException("adjustViewBounds not supported.");
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (cDisableCircularTransformation) {
            super.onDraw(canvas);
            return;
        }

        if (cBitmap == null) {
            return;
        }

        canvas.drawCircle(cDrawableRect.centerX(), cDrawableRect.centerY(), cDrawableRadius, cBitmapPaint);
        if (cBorderWidth > 0) {
            canvas.drawCircle(cBorderRect.centerX(), cBorderRect.centerY(), cBorderRadius, cBorderPaint);
        }
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        setup();
    }

    @Override
    public void setPadding(int left, int top, int right, int bottom) {
        super.setPadding(left, top, right, bottom);
        setup();
    }

    @Override
    public void setPaddingRelative(int start, int top, int end, int bottom) {
        super.setPaddingRelative(start, top, end, bottom);
        setup();
    }

    public int getBorderColor() {
        return cBorderColor;
    }

    public void setBorderColor(@ColorInt int borderColor) {
        if (borderColor == cBorderColor) {
            return;
        }

        cBorderColor = borderColor;
        cBorderPaint.setColor(cBorderColor);
        invalidate();
    }

    public void setBorderColorResource(@ColorRes int borderColorRes) {
        setBorderColor(getContext().getResources().getColor(borderColorRes));
    }


    public int getBorderWidth() {
        return cBorderWidth;
    }

    public void setBorderWidth(int borderWidth) {
        if (borderWidth == cBorderWidth) {
            return;
        }

        cBorderWidth = borderWidth;
        setup();
    }

    public boolean isDisableCircularTransformation() {
        return cDisableCircularTransformation;
    }

    public void setDisableCircularTransformation(boolean disableCircularTransformation) {
        if (cDisableCircularTransformation == disableCircularTransformation) {
            return;
        }

        cDisableCircularTransformation = disableCircularTransformation;
        initializeBitmap();
    }

    @Override
    public void setImageBitmap(Bitmap bm) {
        super.setImageBitmap(bm);
        initializeBitmap();
    }

    @Override
    public void setImageDrawable(Drawable drawable) {
        super.setImageDrawable(drawable);
        initializeBitmap();
    }

    @Override
    public void setImageResource(@DrawableRes int resId) {
        super.setImageResource(resId);
        initializeBitmap();
    }

    @Override
    public void setImageURI(Uri uri) {
        super.setImageURI(uri);
        initializeBitmap();
    }


    @Override
    public void setColorFilter(ColorFilter cf) {
        if (cf == cColorFilter) {
            return;
        }

        cColorFilter = cf;
        applyColorFilter();
        invalidate();
    }

    @Override
    public ColorFilter getColorFilter() {
        return cColorFilter;
    }


    private void applyColorFilter() {
        if (cBitmapPaint != null) {
            cBitmapPaint.setColorFilter(cColorFilter);
        }
    }

    private Bitmap getBitmapFromDrawable(Drawable drawable) {
        if (drawable == null) {
            return null;
        }

        if (drawable instanceof BitmapDrawable) {
            return ((BitmapDrawable) drawable).getBitmap();
        }

        try {
            Bitmap bitmap;

            if (drawable instanceof ColorDrawable) {
                bitmap = Bitmap.createBitmap(COLORDRAWABLE_DIMENSION, COLORDRAWABLE_DIMENSION, BITMAP_CONFIG);
            } else {
                bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), BITMAP_CONFIG);
            }

            Canvas canvas = new Canvas(bitmap);
            drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
            drawable.draw(canvas);
            return bitmap;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    private void initializeBitmap() {
        if (cDisableCircularTransformation) {
            cBitmap = null;
        } else {
            cBitmap = getBitmapFromDrawable(getDrawable());
        }
        setup();
    }

    private void setup() {
        if (!cReady) {
            cSetupPending = true;
            return;
        }

        if (getWidth() == 0 && getHeight() == 0) {
            return;
        }

        if (cBitmap == null) {
            invalidate();
            return;
        }

        cBitmapShader = new BitmapShader(cBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);

        cBitmapPaint.setAntiAlias(true);
        cBitmapPaint.setShader(cBitmapShader);

        cBorderPaint.setStyle(Paint.Style.STROKE);
        cBorderPaint.setAntiAlias(true);
        cBorderPaint.setColor(cBorderColor);
        cBorderPaint.setStrokeWidth(cBorderWidth);


        cBitmapHeight = cBitmap.getHeight();
        cBitmapWidth = cBitmap.getWidth();

        cBorderRect.set(calculateBounds());
        cBorderRadius = Math.min((cBorderRect.height() - cBorderWidth) / 2.0f, (cBorderRect.width() - cBorderWidth) / 2.0f);

        cDrawableRect.set(cBorderRect);
        if (cBorderWidth > 0) {
            cDrawableRect.inset(cBorderWidth - 1.0f, cBorderWidth - 1.0f);
        }
        cDrawableRadius = Math.min(cDrawableRect.height() / 2.0f, cDrawableRect.width() / 2.0f);

        applyColorFilter();
        updateShaderMatrix();
        invalidate();
    }

    private RectF calculateBounds() {
        int availableWidth = getWidth() - getPaddingLeft() - getPaddingRight();
        int availableHeight = getHeight() - getPaddingTop() - getPaddingBottom();

        int sideLength = Math.min(availableWidth, availableHeight);

        float left = getPaddingLeft() + (availableWidth - sideLength) / 2f;
        float top = getPaddingTop() + (availableHeight - sideLength) / 2f;

        return new RectF(left, top, left + sideLength, top + sideLength);
    }

    private void updateShaderMatrix() {
        float scale;
        float dx = 0;
        float dy = 0;

        cShaderMatrix.set(null);

        if (cBitmapWidth * cDrawableRect.height() > cDrawableRect.width() * cBitmapHeight) {
            scale = cDrawableRect.height() / (float) cBitmapHeight;
            dx = (cDrawableRect.width() - cBitmapWidth * scale) * 0.5f;
        } else {
            scale = cDrawableRect.width() / (float) cBitmapWidth;
            dy = (cDrawableRect.height() - cBitmapHeight * scale) * 0.5f;
        }

        cShaderMatrix.setScale(scale, scale);
        cShaderMatrix.postTranslate((int) (dx + 0.5f) + cDrawableRect.left, (int) (dy + 0.5f) + cDrawableRect.top);

        cBitmapShader.setLocalMatrix(cShaderMatrix);
    }
}
